﻿using System.Collections;
using System.Collections.Generic;
using UnityEngine;


public class VolumeShadow1 : MonoBehaviour
{
    public enum ShadowVolumeType
    {
        ZPass,
        CarmackReverse
    }
    private GameObject _shadowObject;
    private ShadowVolumeType _shadowType = ShadowVolumeType.CarmackReverse;
    // Start is called before the first frame update

    void Awake() 
    {
        _shadowObject = new GameObject();
        _shadowObject.transform.parent = transform;
        _shadowObject.transform.localPosition = Vector3.zero;
        _shadowObject.transform.localRotation = Quaternion.identity;
        _shadowObject.AddComponent<MeshFilter>();
        switch(_shadowType)
        {
            case ShadowVolumeType.CarmackReverse:
                _shadowObject.AddComponent<MeshRenderer>().material = new Material(Shader.Find("Unlit/ShadowVolumeCarmackReverse"));
            break;
            case ShadowVolumeType.ZPass:
                _shadowObject.AddComponent<MeshRenderer>().material = new Material(Shader.Find("Unlit/ShadowVolume"));
            break;
        }
        _shadowObject.layer = LayerMask.NameToLayer("ShadowVolume");
    }

    void Start()
    {
        Light[] allDirectionalLight = Light.GetLights(LightType.Directional, 0);
        if(allDirectionalLight.Length > 0)
        {
            _RefreshShadowVolumeForDirectionalLight(allDirectionalLight[0].transform);
        }
    }

    // Update is called once per frame
    void Update()
    {
        
    }

    private void _RefreshShadowVolumeForPointLight(Vector3 worldPos)
    {
        Vector3 lightPos = worldPos;
        Vector4 lightPos4 = new Vector4(lightPos.x, lightPos.y, lightPos.z, 1);
        Vector4 localLightPos4 = transform.worldToLocalMatrix * lightPos4;
        Vector3 localLightPos = new Vector3(localLightPos4.x, localLightPos4.y, localLightPos4.z);
        MeshFilter mf = GetComponent<MeshFilter>();
        if(mf != null)
        {
            Mesh mesh = mf.mesh;
            Vector3[] verts = mesh.vertices;
            int[] indices = mesh.triangles;
            List<Vector3> shadowVolumeVerts = new List<Vector3>();
            List<int> shadowVolumeIndices = new List<int>();
            List<Vector2> shadowUVs = new List<Vector2>();
            int submeshcount = mesh.subMeshCount;
            for(int i = 0; i + 2 < indices.Length; i+=3)
            {
                int a = indices[i];
                int b = indices[i+1];
                int c = indices[i+2];
                Vector3 v1 = verts[a] - verts[b];
                Vector3 v2 = verts[c] - verts[b];
                Vector3 cross = Vector3.Cross(v2, v1);
                float dir = Vector3.Dot(cross, localLightPos - verts[b]);
                if(dir < 0)
                {
                    int t = indices[i];
                    indices[i] = indices[i+2];
                    indices[i+2] = t;
                }
                for(int j = 0; j < 3; j++)
                {
                    a = indices[i + j];
                    b = indices[i + (j + 1) % 3];
                    int indexBase = shadowVolumeVerts.Count;
                    shadowVolumeVerts.Add(new Vector3(verts[b].x, verts[b].y, verts[b].z));
                    shadowUVs.Add(new Vector2(1, 0));
                    shadowVolumeVerts.Add(new Vector3(verts[a].x, verts[a].y, verts[a].z));
                    shadowUVs.Add(new Vector2(1, 0));
                    shadowVolumeVerts.Add(new Vector3(verts[a].x - localLightPos.x, verts[a].y - localLightPos.y, verts[a].z - localLightPos.z));
                    shadowUVs.Add(new Vector2(0, 0));
                    shadowVolumeVerts.Add(new Vector3(verts[b].x - localLightPos.x, verts[b].y - localLightPos.y, verts[b].z - localLightPos.z));
                    shadowUVs.Add(new Vector2(0, 0));
                    shadowVolumeIndices.Add(indexBase);
                    shadowVolumeIndices.Add(indexBase+1);
                    shadowVolumeIndices.Add(indexBase+2);
                    shadowVolumeIndices.Add(indexBase+2);
                    shadowVolumeIndices.Add(indexBase+3);
                    shadowVolumeIndices.Add(indexBase);
                }
            }
            Mesh shadowVolume = new Mesh();
            shadowVolume.SetVertices(shadowVolumeVerts);
            shadowVolume.SetTriangles(shadowVolumeIndices, 0);
            shadowVolume.SetUVs(0, shadowUVs);
            _shadowObject.GetComponent<MeshFilter>().mesh = shadowVolume;
        }
    }

    
    private void _RefreshShadowVolumeForDirectionalLight(Transform light)
    {
        Vector3 lightDir = light.forward;
        Vector3 localLightDir = transform.worldToLocalMatrix * lightDir;
        MeshFilter mf = GetComponent<MeshFilter>();
        if(mf != null)
        {
            Mesh mesh = mf.mesh;
            Vector3[] verts = mesh.vertices;
            int[] indices = mesh.triangles;
            List<Vector3> shadowVolumeVerts = new List<Vector3>();
            List<int> shadowVolumeIndices = new List<int>();
            List<Vector2> shadowUVs = new List<Vector2>();
            int submeshcount = mesh.subMeshCount;
            for(int i = 0; i + 2 < indices.Length; i+=3)
            {
                int a = indices[i];
                int b = indices[i+1];
                int c = indices[i+2];
                
                Vector3 v1 = verts[a] - verts[b];
                Vector3 v2 = verts[c] - verts[b];
                Vector3 cross = Vector3.Cross(v2, v1);
                float dir = Vector3.Dot(cross, -localLightDir);
                if(dir < 0)
                {
                    int t = indices[i];
                    indices[i] = indices[i+2];
                    indices[i+2] = t;
                }
                for(int j = 0; j < 3; j++)
                {
                    a = indices[i + j];
                    b = indices[i + (j + 1) % 3];
                    int indexBase = shadowVolumeVerts.Count;
                    shadowVolumeVerts.Add(new Vector3(verts[b].x, verts[b].y, verts[b].z));
                    shadowUVs.Add(new Vector2(1, 0));
                    shadowVolumeVerts.Add(new Vector3(verts[a].x, verts[a].y, verts[a].z));
                    shadowUVs.Add(new Vector2(1, 0));
                    shadowVolumeVerts.Add(new Vector3(localLightDir.x, localLightDir.y, localLightDir.z));
                    shadowUVs.Add(new Vector2(0, 0));
                    shadowVolumeIndices.Add(indexBase);
                    shadowVolumeIndices.Add(indexBase+1);
                    shadowVolumeIndices.Add(indexBase+2);
                }
                
                if(_shadowType == ShadowVolumeType.CarmackReverse)
                {
                    int indexBase = shadowVolumeVerts.Count;
                    a = indices[i];
                    b = indices[i+1];
                    c = indices[i+2];
                    //add shadow volume near plane
                    shadowVolumeVerts.Add(new Vector3(verts[a].x, verts[a].y, verts[a].z));
                    shadowUVs.Add(new Vector2(1, 0));
                    shadowVolumeVerts.Add(new Vector3(verts[b].x, verts[b].y, verts[b].z));
                    shadowUVs.Add(new Vector2(1, 0));
                    shadowVolumeVerts.Add(new Vector3(verts[c].x, verts[c].y, verts[c].z));
                    shadowUVs.Add(new Vector2(1, 0));
                    shadowVolumeIndices.Add(indexBase);
                    shadowVolumeIndices.Add(indexBase+1);
                    shadowVolumeIndices.Add(indexBase+2);
                }
            }
            Mesh shadowVolume = new Mesh();
            shadowVolume.SetVertices(shadowVolumeVerts);
            shadowVolume.SetTriangles(shadowVolumeIndices, 0);
            shadowVolume.SetUVs(0, shadowUVs);
            _shadowObject.GetComponent<MeshFilter>().mesh = shadowVolume;
        }
    }
}
